The libart library | |||
---|---|---|---|
<<< Previous Page | Home | Next Page >>> |
To express this new concept (that is, partial coverage of a pixel rather than Black or White), libart redefines winding numbers and uses a special data structure which is of importance to describe them: ArtSVPRenderAAStep.
To understand how they are used, we'll use the example shown in the figure below. On the bottom row of pixels, pixel 3 would be represented by the step (x=1, delta=+0.3), pixel 4 by the step (x=2, delta=+0.67) and pixels 5 and 6 by the step (x=3, delta=+0.03).
The bottom row of pixels is rendered as folows: we begin by rendering the first step which says that pixel 1 on this row is 30% covered by the outline (delta=0.3 -> 30%). The second step tells us that the second pixel on this row is (30+67)=97% covered by the outline (the difference -the delta- is of +0.67). Finally, the third step tells us that pixels 3 and folowing on this row are (97+3)=100% covered by the outline. The fourth step would tell us where the pixels are not 100% covered anymore. To summarize, on a given row of pixels, each step gives us the delta (that is, the difference in coverage) relative to the previous step of the current pixel.
The raster version of an outline can thus be represented relative to an output buffer by an array of arrays of ArtSVPRenderAASteps. Each array of ArtSVPRenderAASteps represents a row of pixels of the output buffer and each step of the array of ArtSVPRenderAASteps represents a run (that is, adjacent pixels with same color characteristics).
People who want to render the real pixels in the output buffer just need to calculate running sums over the steps for each line of the output buffer. The running sum represents the degree of presence of the outline over the background: the outline is easy to composite over the backgound.
art_svp_render_aa exports the API which offers the interface described in the previous section: a callback is called on each of the output buffer's line with an array of ArtSVPRenderAASteps as parameter. This callback just needs to calculate the running sum for this line and render the real pixels in a pixel buffer.
art_rgb_svp_aa and art_rgb_svp_alpha both use internally art_svp_render_aa by providing different callbacks. Their code is pretty simple and can be found in art_rgb_svp.c (the code for their associated callbacks can be found in the same file).
The interesting piece of code is indeed art_rgb_svp_aa. This code implements a pretty classic Plane Sweep algorithm: it loops over the output buffer's lines and calculates for each line the steps for the callback. To calculate the steps, it maintains over the lines the list of segments crossing the current line.
for (y = 0; y < height; y++) { add_new_active_segments (active_segments, svp); process_line (active_segments, y); remove_old_active_segments (active_segmnts); } |
Let's first have a look at what the SVP data structure looks like.
typedef struct _ArtSVP ArtSVP; typedef struct _ArtSVPSeg ArtSVPSeg; struct _ArtSVPSeg { int n_points; int dir; /* == 0 for "up", 1 for "down" */ ArtDRect bbox; ArtPoint *points; }; struct _ArtSVP { int n_segs; ArtSVPSeg segs[1]; }; |
for (y = y0; y < y1; y++) { /* insert new active segments */ for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++) { if (svp->segs[i].bbox.y1 > y && svp->segs[i].bbox.x0 < x1) { seg = &svp->segs[i]; /* move cursor to topmost vector which overlaps [y,y+1) */ for (curs = 0; seg->points[curs + 1].y < y; curs++); cursor[i] = curs; dy = seg->points[curs + 1].y - seg->points[curs].y; if (fabs (dy) >= EPSILON) seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) / dy; else seg_dx[i] = 1e12; seg_x[i] = seg->points[curs].x + (y - seg->points[curs].y) * seg_dx[i]; art_svp_render_insert_active (i, active_segs, n_active_segs++, seg_x, seg_dx); } } /* here, process the current list of active segments.*/ /* remove old segments from active list */ if (seg->points[curs].y >= y + 1) { curs--; cursor[seg_index] = curs; seg_x[seg_index] += seg_dx[seg_index]; } else { art_svp_render_delete_active (active_segs, j--, --n_active_segs); } } |
Because the code which calculates the different steps for each line of the output buffer is not that easy to get right quickly, here is included a simple diagram which outlines the meaning of the variables of the code:
<<< Previous Page | Home | Next Page >>> | |
Memory Footprint | Intersector |